package com.wwg.util.excel;

import com.google.common.base.Preconditions;
import com.google.common.base.Stopwatch;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.wwg.entity.PagingList;
import com.wwg.entity.PagingVo;
import com.wwg.util.RegexUtil;
import com.wwg.util.WWGAssertUtil;
import com.wwg.util.WWGBeanFactory;
import com.wwg.util.ZipUtil;
import com.wwg.util.file.DownloadUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Function;

/**
 * @description excel工具类
 * @systemName 融资平台
 * @author yyh
 * @date 2019年12月13日 下午2:46:31
 * @version V2.0
 */
public class ExcelUtil<E, P> {
	private static final Logger LOGGER = LoggerFactory.getLogger(ExcelUtil.class);
	private static final ThreadPoolExecutor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(4, 8, 2, TimeUnit.SECONDS,
			new LinkedBlockingQueue<Runnable>(100), new ThreadFactoryBuilder().setNameFormat("excelThreadPool-%d")
					.build());
	private static final String ZIP = "zip/";
	@SuppressWarnings("rawtypes")
	private static final ExcelUtil DEFAULT = new ExcelUtil();

	private ExcelUtil() {
	}

	@Deprecated
	public static <T> void download(HttpServletRequest request, HttpServletResponse response, String fileName,
			List<T> list, Function<List<T>, InputStream> function) {
		Stopwatch stopwatch = Stopwatch.createStarted();
		InputStreamDecorator inputStreamDecorator = zipExcel(fileName, list, function);
		InputStream is = inputStreamDecorator.getInputStream();
		try {
			if (inputStreamDecorator.getTemDir() != null) {
				DownloadUtils.download(request, response, is, fileName + ".zip");
			} else {
				DownloadUtils.download(request, response, is, fileName);
			}
			long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("下载文件，耗时:{}ms", elapsed);
			}

		} finally {
			if (inputStreamDecorator.getTemDir() != null) {
				THREAD_POOL_EXECUTOR.execute(new Runnable() {
					@Override
					public void run() {
						try {
							FileUtils.deleteDirectory(new File(inputStreamDecorator.getTemDir()));
							FileUtils.deleteDirectory(new File(inputStreamDecorator.getTemDir().substring(0,
									inputStreamDecorator.getTemDir().lastIndexOf("/"))
									+ ZIP));
						} catch (IOException e) {
							LOGGER.error("删除文件异常", e.getMessage());
						}
					}
				});
			}
		}
	}

	public static String getFilePath() {
		final String temDir = generateTemDir();
		ExcelConfig excelConfig = WWGBeanFactory.getBean(ExcelConfig.class);
		if (WWGAssertUtil.isEmpty(excelConfig.getFilePath())) {
			return getTemplatePath(temDir);
		} else {
			return excelConfig.getFilePath() + temDir + "/";
		}
	}

	public static int getMaxRow() {
		ExcelConfig excelConfig = WWGBeanFactory.getBean(ExcelConfig.class);
		if (WWGAssertUtil.isEmpty(excelConfig.getMaxRow())) {
			return ExcelConfig.MAX_ROW;
		} else {
			return excelConfig.getMaxRow();
		}
	}

	public static int getTimeOut() {
		ExcelConfig excelConfig = WWGBeanFactory.getBean(ExcelConfig.class);
		if (WWGAssertUtil.isEmpty(excelConfig.getTimeOut())) {
			return ExcelConfig.TIME_OUT;
		} else {
			return excelConfig.getTimeOut();
		}
	}

	public static int getPageSize() {
		ExcelConfig excelConfig = WWGBeanFactory.getBean(ExcelConfig.class);
		if (WWGAssertUtil.isEmpty(excelConfig.getPageSize())) {
			return ExcelConfig.PAGE_SIZE;
		} else {
			return excelConfig.getPageSize();
		}
	}

	@Deprecated
	public static <T> InputStreamDecorator zipExcel(String fileName, List<T> list,
			Function<List<T>, ? extends InputStream> function) {
		final String regEx = "^.*(\\.xls|\\.xlsx)$";
		Preconditions.checkArgument(RegexUtil.match(regEx, fileName), "文件名:%s,不符合excel文件命名格式", fileName);
		Preconditions.checkNotNull(function);
		// excel文件最大行数
		final int maxRow = getMaxRow();
		Preconditions.checkNotNull(list);
		if (list.size() > maxRow) {
			final int fileSize = list.size() % maxRow == 0 ? list.size() / maxRow : list.size() / maxRow + 1;
			final CountDownLatch countDownLatch = new CountDownLatch(fileSize);
			final String filePath = getFilePath();
			int count = list.size();
			int fromIndex = 0;
			int toIndex = maxRow;
			int fileNameIndex = 0;
			/**
			 * list数据拆分生成多个文件,每个文件最多65500行
			 */
			final String suffix = RegexUtil.find(regEx, fileName);
			final String prefix = fileName.replace(suffix, "");
			while (count > 0) {
				List<T> subList = list.subList(fromIndex, toIndex);
				InputStream is = function.apply(subList);
				final int index = ++fileNameIndex;
				THREAD_POOL_EXECUTOR.execute(new Runnable() {
					@Override
					public void run() {
						generateFile(is, filePath, prefix + index + suffix);
						countDownLatch.countDown();
						LOGGER.info("countDown");
					}
				});

				count -= maxRow;
				fromIndex += maxRow;
				toIndex += count > maxRow ? maxRow : count;
			}

			try {
				countDownLatch.await(60, TimeUnit.SECONDS);
			} catch (InterruptedException e1) {
				LOGGER.error("闭锁异常,msg={}", e1.getMessage());
			}

			/**
			 * 生成文件个数校验
			 */
			File sourceFile = new File(filePath);
			Preconditions.checkArgument(fileSize == sourceFile.listFiles().length, "生成文件个数异常,本应生成文件个数:%s,实际生成文件个数:%s",
					fileSize, sourceFile.listFiles().length);

			final String zipFileName = getZipPath(filePath) + fileName + ".zip";
			/**
			 * 压缩文件
			 */
			try (BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(zipFileName))) {
				ZipUtil.toZip(filePath, fos, false);
			} catch (IOException e) {
				LOGGER.error("压缩文件错误,msg={}", e.getMessage());
			}

			/**
			 * 读取压缩文件
			 */
			File targetFile = new File(zipFileName);
			try {
				InputStream in = new FileInputStream(targetFile);
				return InputStreamDecorator.newInstance(in, filePath);
			} catch (FileNotFoundException e) {
				LOGGER.error("读取压缩文件错误,msg={}", e.getMessage());
			}

		} else {
			return InputStreamDecorator.newInstance(function.apply(list), null);
		}

		return null;
	}

	private static String getZipPath(String filePath) {
		String zipPath = filePath.substring(0, filePath.lastIndexOf("/")) + ZIP;
		File pathFile = new File(zipPath);
		if (!pathFile.exists()) {
			pathFile.mkdirs();
		}
		return zipPath;
	}

	/**
	 * @description 获取临时文件目录
	 * @return
	 * @author yyh
	 * @date 2019年12月13日 下午5:26:02
	 */
	private static String generateTemDir() {
		//			ISerialNumberService serialNumberService = WWGBeanFactory.getBean(ISerialNumberService.class);
//			return serialNumberService.generateFileNameKey();
		return UUID.randomUUID().toString();
	}

	private static String getTemplatePath(String temDir) {
		String systemName = System.getProperty("os.name");
		String path = "";
		// 判断当前环境，如果是Windows 要截取路径的第一个 '/'
		final String windows = "Windows";
		if (!StringUtils.isBlank(systemName) && systemName.indexOf(windows) != -1) {
			path = ExcelUtil.class.getResource("/").getFile().toString().substring(1);
			final String classesPath = "target/classes";
			if (path.indexOf(classesPath) != -1) {
				path = path.substring(0, path.lastIndexOf("classes")) + "jfpt-sys/WEB-INF/";
			} else {
				path = path.substring(0, path.lastIndexOf("classes"));
			}
		} else {
			path = ExcelUtil.class.getResource("/").getFile().toString();
		}
		String templatePath = path.replace("classes/", "") + "tp/" + temDir + "/";

		return templatePath;
	}

	private static void generateFile(InputStream is, String filePath, String fileName) {
		try {
			Stopwatch stopwatch = Stopwatch.createStarted();
			File pathFile = new File(filePath);
			if (!pathFile.exists()) {
				pathFile.mkdirs();
			}
			String relFilePath = filePath + fileName;
			File file = new File(relFilePath);
			if (!file.exists()) {
				file.createNewFile();
			}

			try (BufferedInputStream bis = new BufferedInputStream(is);
					BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file));) {
				int length = -1;
				byte[] tmp = new byte[1024];
				while ((length = bis.read(tmp)) != -1) {
					bos.write(tmp, 0, length);
				}
				long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
				if (LOGGER.isDebugEnabled()) {
					LOGGER.debug("生成文件，耗时:{}ms", elapsed);
				}
			}

		} catch (IOException e) {
			LOGGER.error(e.getMessage(), e);
		}
	}

	private static class InputStreamDecorator {
		private InputStream inputStream;
		private String temDir;

		private InputStreamDecorator(InputStream inputStream, String temDir) {
			this.inputStream = inputStream;
			this.temDir = temDir;
		}

		private static InputStreamDecorator newInstance(InputStream inputStream, String temDir) {
			return new InputStreamDecorator(inputStream, temDir);
		}

		public InputStream getInputStream() {
			return inputStream;
		}

		public void setInputStream(InputStream inputStream) {
			this.inputStream = inputStream;
		}

		public String getTemDir() {
			return temDir;
		}

		public void setTemDir(String temDir) {
			this.temDir = temDir;
		}
	}

	private InputStreamDecorator zip(String fileName, P condition, Function<P, List<E>> acquireResultSetFunction,
			Function<List<E>, ? extends InputStream> generateExcelFunction) {
		final String regEx = "^.*(\\.xls|\\.xlsx)$";
		Preconditions.checkArgument(RegexUtil.match(regEx, fileName), "文件名:%s,不符合excel文件命名格式", fileName);
		Preconditions.checkNotNull(acquireResultSetFunction);
		Preconditions.checkNotNull(generateExcelFunction);
		// excel文件最大行数
		final int maxRow = getMaxRow();
		// 前端导出每一页的数据长度
		int len = getPageSize();

//		if (condition instanceof PagerModel) {
//			PagerModel condi = (PagerModel) condition;
//			if ((condi.getPage() * len - condi.getLength()) >= maxRow) {
//				len = condi.getLength() - (condi.getPage() - 1) * len;
//			}
////			if (condi.getLength() < maxRow) {
////				len = condi.getLength();
////			}
//			condi.setRows(len);
//
//		}
		if (condition instanceof PagingVo) {
			PagingVo condi = (PagingVo) condition;
			if ((condi.getPage() * len - condi.getLength()) >= maxRow) {
				len = condi.getLength() - (condi.getPage() - 1) * len;
			}
//			if (condi.getLength() < maxRow) {
//				len = condi.getLength();
//			}
			condi.setRows(len);
		}

//		if (condition instanceof PagerModel) {
//			com.wsn.jfpt.common.model.PagerModel condi = (com.wsn.jfpt.common.model.PagerModel) condition;
//			if ((condi.getPage() * len - condi.getLength()) >= maxRow) {
//				len = condi.getLength() - (condi.getPage() - 1) * len;
//			}
////			if (condi.getLength() < maxRow) {
////				len = condi.getLength();
////			}
//			condi.setRows(len);
//		}

		// 如果数据长度小于excel文件最大行数，则不需要分隔文件
		if (len <= maxRow) {
			return InputStreamDecorator.newInstance(
					generateExcelFunction.apply(acquireResultSetFunction.apply(condition)), null);
		}

		PagingList<P> pagingList = new PagingList<>(maxRow, condition);
		final CountDownLatch countDownLatch = new CountDownLatch(pagingList.size());
		final String filePath = getFilePath();
		final String suffix = RegexUtil.find(regEx, fileName);
		final String prefix = fileName.replace(suffix, "");
		int fileNameIndex = 0;
		/**
		 * list数据拆分生成多个文件,每个文件最多65500行
		 */
		for (P pagingCodition : pagingList) {
			final int index = ++fileNameIndex;
			THREAD_POOL_EXECUTOR.execute(new Runnable() {
				@Override
				public void run() {
					List<E> list = acquireResultSetFunction.apply(pagingCodition);
					InputStream is = generateExcelFunction.apply(list);
					generateFile(is, filePath, prefix + index + suffix);
					countDownLatch.countDown();
					LOGGER.info("countDown");
				}
			});
		}
		try {
			countDownLatch.await(getTimeOut(), TimeUnit.SECONDS);
		} catch (InterruptedException e1) {
			LOGGER.error("闭锁异常,msg={}", e1.getMessage());
		}

		/**
		 * 生成文件个数校验
		 */
		File sourceFile = new File(filePath);
		Preconditions.checkArgument(pagingList.size() == sourceFile.listFiles().length,
				"生成文件个数异常,本应生成文件个数:%s,实际生成文件个数:%s", pagingList.size(), sourceFile.listFiles().length);

		final String zipFileName = getZipPath(filePath) + fileName + ".zip";
		/**
		 * 压缩文件
		 */
		try (BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(zipFileName))) {
			ZipUtil.toZip(filePath, fos, false);
		} catch (IOException e) {
			LOGGER.error("压缩文件错误,msg={}", e.getMessage());
		}

		/**
		 * 读取压缩文件
		 */
		File targetFile = new File(zipFileName);
		try {
			InputStream in = new FileInputStream(targetFile);
			return InputStreamDecorator.newInstance(in, filePath);
		} catch (FileNotFoundException e) {
			LOGGER.error("读取压缩文件错误,msg={}", e.getMessage());
		}

		return null;
	}

	@SuppressWarnings("unchecked")
	public static <E, P> InputStreamDecorator zipExcel(String fileName, P condition,
			Function<P, List<E>> acquireResultSetFunction,
			Function<List<E>, ? extends InputStream> generateExcelfunction) {
		return DEFAULT.zip(fileName, condition, acquireResultSetFunction, generateExcelfunction);
	}

	/**  
	 * @description excel文件下载导出主方法
	 * @param request
	 * @param response
	 * @param fileName
	 * @param condition 查询业务方法的查询条件
	 * @param acquireResultSetFunction 获取查询结果的函数
	 * @param generateExcelfunction 生产excel文件的函数
	 * @author yyh  
	 * @date 2020年6月24日   上午9:39:00
	 */  
	public static <E, P> void download(HttpServletRequest request, HttpServletResponse response, String fileName,
			P condition, Function<P, List<E>> acquireResultSetFunction,
			Function<List<E>, ? extends InputStream> generateExcelfunction) {
		Stopwatch stopwatch = Stopwatch.createStarted();
		final InputStreamDecorator inputStreamDecorator = zipExcel(fileName, condition, acquireResultSetFunction,
				generateExcelfunction);
		final InputStream is = inputStreamDecorator.getInputStream();

		try {
			if (inputStreamDecorator.getTemDir() != null) {
				DownloadUtils.download(request, response, is, fileName + ".zip");
			} else {
				DownloadUtils.download(request, response, is, fileName);
			}
			long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("下载文件，耗时:{}ms", elapsed);
			}

		} finally {
			if (inputStreamDecorator.getTemDir() != null) {
				THREAD_POOL_EXECUTOR.execute(new Runnable() {
					@Override
					public void run() {
						try {
							FileUtils.deleteDirectory(new File(inputStreamDecorator.getTemDir()));
							FileUtils.deleteDirectory(new File(inputStreamDecorator.getTemDir().substring(0,
									inputStreamDecorator.getTemDir().lastIndexOf("/"))
									+ ZIP));
						} catch (IOException e) {
							LOGGER.error("删除文件异常", e.getMessage());
						}
					}
				});
			}
		}
	}
	
	
	/**
	 * 新版生成excel（含总计），超过最大行数，会分割成多个excel，最后一个excel的endFlag为true
	 * @param <E>
	 * @param <P>
	 * @param request
	 * @param response
	 * @param fileName
	 * @param condition
	 * @param acquireResultSetFunction
	 * @param generateExcelfunction
	 */
	public static <E, P> void download(HttpServletRequest request, HttpServletResponse response, String fileName,
			P condition, Function<P, List<E>> acquireResultSetFunction,
			BiFunction<List<E>, Boolean,? extends InputStream> generateExcelfunction) {
		Stopwatch stopwatch = Stopwatch.createStarted();
		final InputStreamDecorator inputStreamDecorator = zipExcel(fileName, condition, acquireResultSetFunction,
				generateExcelfunction);
		final InputStream is = inputStreamDecorator.getInputStream();

		try {
			if (inputStreamDecorator.getTemDir() != null) {
				String prefix = fileName.replace(RegexUtil.find("^.*(\\.xls|\\.xlsx)$", fileName), "");
				DownloadUtils.download(request, response, is, prefix + ".zip");
			} else {
				DownloadUtils.download(request, response, is, fileName);
			}
			long elapsed = stopwatch.elapsed(TimeUnit.MILLISECONDS);
			if (LOGGER.isDebugEnabled()) {
				LOGGER.debug("下载文件，耗时:{}ms", elapsed);
			}

		} finally {
			if (inputStreamDecorator.getTemDir() != null) {
				THREAD_POOL_EXECUTOR.execute(new Runnable() {
					@Override
					public void run() {
						try {
							FileUtils.deleteDirectory(new File(inputStreamDecorator.getTemDir()));
							FileUtils.deleteDirectory(new File(inputStreamDecorator.getTemDir().substring(0,
									inputStreamDecorator.getTemDir().lastIndexOf("/"))
									+ ZIP));
						} catch (IOException e) {
							LOGGER.error("删除文件异常", e.getMessage());
						}
					}
				});
			}
		}
	}

	@SuppressWarnings("unchecked")
	public static <E, P> InputStreamDecorator zipExcel(String fileName, P condition,
			Function<P, List<E>> acquireResultSetFunction,
			BiFunction<List<E>, Boolean,? extends InputStream> generateExcelfunction) {
		return DEFAULT.zip(fileName, condition, acquireResultSetFunction, generateExcelfunction);
	}

	private InputStreamDecorator zip(String fileName, P condition, Function<P, List<E>> acquireResultSetFunction,
			BiFunction<List<E>, Boolean,? extends InputStream> generateExcelFunction) {
		final String regEx = "^.*(\\.xls|\\.xlsx)$";
		Preconditions.checkArgument(RegexUtil.match(regEx, fileName), "文件名:%s,不符合excel文件命名格式", fileName);
		Preconditions.checkNotNull(acquireResultSetFunction);
		Preconditions.checkNotNull(generateExcelFunction);
		// excel文件最大行数
		final int maxRow = getMaxRow();
		// 前端导出每一页的数据长度
		int len = getPageSize();

//		if (condition instanceof PagerModel) {
//			PagerModel condi = (PagerModel) condition;
//			if ((condi.getPage() * len - condi.getLength()) >= maxRow) {
//				len = condi.getLength() - (condi.getPage() - 1) * len;
//			}
////			if (condi.getLength() < maxRow) {
////				len = condi.getLength();
////			}
//			condi.setRows(len);
//
//		}
		if (condition instanceof PagingVo) {
			PagingVo condi = (PagingVo) condition;
			if ((condi.getPage() * len - condi.getLength()) >= maxRow) {
				len = condi.getLength() - (condi.getPage() - 1) * len;
			}
//			if (condi.getLength() < maxRow) {
//				len = condi.getLength();
//			}
			condi.setRows(len);
		}
//		if (condition instanceof com.wsn.jfpt.common.model.PagerModel) {
//			com.wsn.jfpt.common.model.PagerModel condi = (com.wsn.jfpt.common.model.PagerModel) condition;
//			if ((condi.getPage() * len - condi.getLength()) >= maxRow) {
//				len = condi.getLength() - (condi.getPage() - 1) * len;
//			}
////			if (condi.getLength() < maxRow) {
////				len = condi.getLength();
////			}
//			condi.setRows(len);
//		}

		// 如果数据长度小于excel文件最大行数，则不需要分隔文件
		if (len <= maxRow) {
			return InputStreamDecorator.newInstance(
					generateExcelFunction.apply(acquireResultSetFunction.apply(condition),true), null);
		}

		PagingList<P> pagingList = new PagingList<>(maxRow, condition);
		final CountDownLatch countDownLatch = new CountDownLatch(pagingList.size());
		final String filePath = getFilePath();
		final String suffix = RegexUtil.find(regEx, fileName);
		final String prefix = fileName.replace(suffix, "");
		int fileNameIndex = 0;
		/**
		 * list数据拆分生成多个文件,每个文件最多65500行
		 */
		for (P pagingCodition : pagingList) {
			final int index = ++fileNameIndex;
			THREAD_POOL_EXECUTOR.execute(new Runnable() {
				@Override
				public void run() {
					List<E> list = acquireResultSetFunction.apply(pagingCodition);
					InputStream is = generateExcelFunction.apply(list,index==pagingList.size());
					generateFile(is, filePath, prefix + index + suffix);
					countDownLatch.countDown();
					LOGGER.info("countDown");
				}
			});
		}
		try {
			countDownLatch.await(getTimeOut(), TimeUnit.SECONDS);
		} catch (InterruptedException e1) {
			LOGGER.error("闭锁异常,msg={}", e1.getMessage());
		}

		/**
		 * 生成文件个数校验
		 */
		File sourceFile = new File(filePath);
		Preconditions.checkArgument(pagingList.size() == sourceFile.listFiles().length,
				"生成文件个数异常,本应生成文件个数:%s,实际生成文件个数:%s", pagingList.size(), sourceFile.listFiles().length);

		final String zipFileName = getZipPath(filePath) + prefix + ".zip";
		/**
		 * 压缩文件
		 */
		try (BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(zipFileName))) {
			ZipUtil.toZip(filePath, fos, false);
		} catch (IOException e) {
			LOGGER.error("压缩文件错误,msg={}", e.getMessage());
		}

		/**
		 * 读取压缩文件
		 */
		File targetFile = new File(zipFileName);
		try {
			InputStream in = new FileInputStream(targetFile);
			return InputStreamDecorator.newInstance(in, filePath);
		} catch (FileNotFoundException e) {
			LOGGER.error("读取压缩文件错误,msg={}", e.getMessage());
		}

		return null;
	}

}
